package com.duartepeixinho.P3D.Core.Math
{
	/**
	 * P3D
	 * @version 0.1 
	 * @author Duarte Peixinho	 
	 */
	public class Matrix
	{
		
		public var n11:Number, n12:Number, n13:Number, n14:Number, n21:Number, n22:Number, n23:Number, n24:Number, n31:Number, n32:Number, n33:Number, n34:Number, n41:Number, n42:Number, n43:Number, n44:Number;
		
		
		public function Matrix() 
		{
			
			identify();
			
		}
		
		/**
		 * Identify
		 */
		public function identify():void {
			n11 = 1; n12 = 0; n13 = 0; n14 = 0;
			n21 = 0; n22 = 1; n23 = 0; n24 = 0;
			n31 = 0; n32 = 0; n33 = 1; n34 = 0;
			n41 = 0; n42 = 0; n43 = 0; n44 = 1;
		}
		
		/**
		 * lookAt
		 * @param	eye		Vector3
		 * @param	center	Vector3
		 * @param	up		Vector3
		 */
		public function lookAt(eye:Vector3, center:Vector3, up:Vector3):void {
			
			var forward:Vector3 = center.subVector(eye);
			forward.normalize();
			var side:Vector3 = forward.cross(up);
			side.normalize();			
			var up:Vector3 = side.cross(forward);
			up.normalize();
			
			n11 = side.x;		n12 = up.x;		n13 = -forward.x;	
			n21 = side.y;		n22 = up.y;		n23 = -forward.y;	
			n31 = side.z;		n32 = up.z;		n33 = -forward.z;				
			
			/*n14 = -side.dotProduct(eye);
			n24 = -up.dotProduct(eye);
			n34 = -forward.dotProduct(eye);*/
			
		}
		
		/**
		 * Transform
		 * @param	vector
		 * @return
		 */
		public function transform(vector:Vector3):Vector3 {
			var v:Vector3 = new Vector3();
			
			v.x = this.n11 * vector.x + this.n12 * vector.y + this.n13 * vector.z + this.n14;
			v.y = this.n21 * vector.x + this.n22 * vector.y + this.n23 * vector.z + this.n24;
			v.z = this.n31 * vector.x + this.n32 * vector.y + this.n33 * vector.z + this.n34;		
			return v;
		}
		
		/**
		 * Multiply Self
		 * @param	m	Matrix
		 */
		public function multiplySelf(m:Matrix):void {
			
			var n11:Number = this.n11, n12:Number = this.n12, n13:Number = this.n13, n14:Number = this.n14,
			n21:Number = this.n21, n22:Number = this.n22, n23:Number = this.n23, n24:Number = this.n24,
			n31:Number = this.n31, n32:Number = this.n32, n33:Number = this.n33, n34:Number = this.n34,
			n41:Number = this.n41, n42:Number = this.n42, n43:Number = this.n43, n44:Number = this.n44;
			
			this.n11 = n11 * m.n11 + n12 * m.n21 + n13 * m.n31 + n14 * m.n41;
			this.n12 = n11 * m.n12 + n12 * m.n22 + n13 * m.n32 + n14 * m.n42;
			this.n13 = n11 * m.n13 + n12 * m.n23 + n13 * m.n33 + n14 * m.n43;
			this.n14 = n11 * m.n14 + n12 * m.n24 + n13 * m.n34 + n14 * m.n44;
			
			this.n21 = n21 * m.n11 + n22 * m.n21 + n23 * m.n31 + n24 * m.n41;
			this.n22 = n21 * m.n12 + n22 * m.n22 + n23 * m.n32 + n24 * m.n42;
			this.n23 = n21 * m.n13 + n22 * m.n23 + n23 * m.n33 + n24 * m.n43;
			this.n24 = n21 * m.n14 + n22 * m.n24 + n23 * m.n34 + n24 * m.n44;
			
			this.n31 = n31 * m.n11 + n32 * m.n21 + n33 * m.n31 + n34 * m.n41;
			this.n32 = n31 * m.n12 + n32 * m.n22 + n33 * m.n32 + n34 * m.n42;
			this.n33 = n31 * m.n13 + n32 * m.n23 + n33 * m.n33 + n34 * m.n43;
			this.n34 = n31 * m.n14 + n32 * m.n24 + n33 * m.n34 + n34 * m.n44;
			
			this.n41 = n41 * m.n11 + n42 * m.n21 + n43 * m.n31 + n44 * m.n41;
			this.n42 = n41 * m.n12 + n42 * m.n22 + n43 * m.n32 + n44 * m.n42;
			this.n43 = n41 * m.n13 + n42 * m.n23 + n43 * m.n33 + n44 * m.n43;
			this.n44 = n41 * m.n14 + n42 * m.n24 + n43 * m.n34 + n44 * m.n44;
			
		}
		
		/**
		 * Multiply Scalar
		 * @param	s	Number
		 */
		public function multiplyScalar(s:Number):void {
			this.n11 *= s; this.n12 *= s; this.n13 *= s; this.n14 *= s;
			this.n21 *= s; this.n22 *= s; this.n23 *= s; this.n24 *= s;
			this.n31 *= s; this.n32 *= s; this.n33 *= s; this.n34 *= s;
			this.n41 *= s; this.n42 *= s; this.n43 *= s; this.n44 *= s;
		}
		
		/**
		 * Determinant
		 * @return		Number
		 */
		public function determinant():Number {
			return  n11 * n22 * n33 +
					n21 * n32 * n13 +
					n31 * n12 * n23 +
					n31 * n22 * n13 +
					n11 * n32 * n23 +
					n21 * n12 * n33;
		}
		
		/**
		 * Transpose
		 */
		public function transpose():void {
			var a:Array, b:Array;
			a[0][0] = n11; a[0][1] = n12; a[0][2] = n13; a[0][3] = n14;
			a[1][0] = n21; a[1][1] = n22; a[1][2] = n23; a[1][3] = n24;
			a[2][0] = n31; a[2][1] = n32; a[2][2] = n33; a[2][3] = n34;
			a[3][0] = n41; a[3][1] = n42; a[3][2] = n43; a[3][3] = n44;
			for (var i:uint = 0; i < 4; i++) {
				for (var j:uint = 0; j < 4; j++) {
					b[i][j] = a[j][i];
				}
			}
			n11 = b[0][0]; n12 = b[0][1]; n13 = b[0][2]; n14 = b[0][3];
			n21 = b[1][0]; n22 = b[1][1]; n23 = b[1][2]; n24 = b[1][3];
			n31 = b[2][0]; n32 = b[2][1]; n33 = b[2][2]; n34 = b[2][3];
			n41 = b[3][0]; n42 = b[3][1]; n43 = b[3][2]; n44 = b[3][3];
		}
		
		/**
		 * Clone
		 * @return		Matrix
		 */
		public function clone():Matrix {
			var m:Matrix = new Matrix();
			m.n11 = this.n11; m.n12 = this.n12; m.n13 = this.n13; m.n14 = this.n14;
			m.n21 = this.n21; m.n22 = this.n22; m.n23 = this.n23; m.n24 = this.n24;
			m.n31 = this.n31; m.n32 = this.n32; m.n33 = this.n33; m.n34 = this.n34;
			m.n41 = this.n41; m.n42 = this.n42; m.n43 = this.n43; m.n44 = this.n44;
			return m;
		}
		
		/**
		 * Scale
		 * @param	sx	Number
		 * @param	sy	Number
		 * @param	sz	Number
		 */
		public function scale(sx:Number=1, sy:Number=1, sz:Number=1):void {
			var m:Matrix = new Matrix();
			m.n11 = sx;
			m.n22 = sy;
			m.n33 = sz;
			multiplySelf(m);
		}
		
		/**
		 * Translation
		 * @param	x	Number
		 * @param	y	Number
		 * @param	z	Number
		 */
		public function translate(x:Number = 0, y:Number = 0, z:Number = 0):void {			
			n14 += x;
			n24 += y;
			n34 += z;
		}
		
		/**
		 * Rotation X
		 * @param	angle
		 */
		public function rotateX(angle:Number):void {
			var m:Matrix = new Matrix();
		 
			m.n22 = m.n33 = Math.cos(angle);	
			m.n32 = Math.sin(angle);
			m.n23 = -m.n32;
			multiplySelf(m);
		}
		
		/**
		 * Rotation Y
		 * @param	angle
		 */
		public function rotateY(angle:Number):void {
			var m:Matrix = new Matrix();
		 
			m.n11 = m.n33 = Math.cos(angle); 	
			m.n13 = Math.sin(angle);
			m.n31 = -m.n13;
			multiplySelf(m);
		}
		
		/**
		 * Rotation Z
		 * @param	angle
		 */
		public function rotateZ(angle:Number):void {
			var m:Matrix = new Matrix();
		 
			m.n11 = m.n22 = Math.cos(angle); 
			m.n21 = Math.sin(angle);
			m.n12 = -m.n21;
			multiplySelf(m);
		}
		
		/**
		 * Invert	
		 * @return			Matrix
		 */
		public function invert():Matrix {
			var m2:Matrix = new Matrix();
			var m1:Matrix = this;

			m2.n11 = m1.n23*m1.n34*m1.n42 - m1.n24*m1.n33*m1.n42 + m1.n24*m1.n32*m1.n43 - m1.n22*m1.n34*m1.n43 - m1.n23*m1.n32*m1.n44 + m1.n22*m1.n33*m1.n44;
			m2.n12 = m1.n14*m1.n33*m1.n42 - m1.n13*m1.n34*m1.n42 - m1.n14*m1.n32*m1.n43 + m1.n12*m1.n34*m1.n43 + m1.n13*m1.n32*m1.n44 - m1.n12*m1.n33*m1.n44;
			m2.n13 = m1.n13*m1.n24*m1.n42 - m1.n14*m1.n23*m1.n42 + m1.n14*m1.n22*m1.n43 - m1.n12*m1.n24*m1.n43 - m1.n13*m1.n22*m1.n44 + m1.n12*m1.n23*m1.n44;
			m2.n14 = m1.n14*m1.n23*m1.n32 - m1.n13*m1.n24*m1.n32 - m1.n14*m1.n22*m1.n33 + m1.n12*m1.n24*m1.n33 + m1.n13*m1.n22*m1.n34 - m1.n12*m1.n23*m1.n34;
			m2.n21 = m1.n24*m1.n33*m1.n41 - m1.n23*m1.n34*m1.n41 - m1.n24*m1.n31*m1.n43 + m1.n21*m1.n34*m1.n43 + m1.n23*m1.n31*m1.n44 - m1.n21*m1.n33*m1.n44;
			m2.n22 = m1.n13*m1.n34*m1.n41 - m1.n14*m1.n33*m1.n41 + m1.n14*m1.n31*m1.n43 - m1.n11*m1.n34*m1.n43 - m1.n13*m1.n31*m1.n44 + m1.n11*m1.n33*m1.n44;
			m2.n23 = m1.n14*m1.n23*m1.n41 - m1.n13*m1.n24*m1.n41 - m1.n14*m1.n21*m1.n43 + m1.n11*m1.n24*m1.n43 + m1.n13*m1.n21*m1.n44 - m1.n11*m1.n23*m1.n44;
			m2.n24 = m1.n13*m1.n24*m1.n31 - m1.n14*m1.n23*m1.n31 + m1.n14*m1.n21*m1.n33 - m1.n11*m1.n24*m1.n33 - m1.n13*m1.n21*m1.n34 + m1.n11*m1.n23*m1.n34;
			m2.n31 = m1.n22*m1.n34*m1.n41 - m1.n24*m1.n32*m1.n41 + m1.n24*m1.n31*m1.n42 - m1.n21*m1.n34*m1.n42 - m1.n22*m1.n31*m1.n44 + m1.n21*m1.n32*m1.n44;
			m2.n32 = m1.n14*m1.n32*m1.n41 - m1.n12*m1.n34*m1.n41 - m1.n14*m1.n31*m1.n42 + m1.n11*m1.n34*m1.n42 + m1.n12*m1.n31*m1.n44 - m1.n11*m1.n32*m1.n44;
			m2.n33 = m1.n13*m1.n24*m1.n41 - m1.n14*m1.n22*m1.n41 + m1.n14*m1.n21*m1.n42 - m1.n11*m1.n24*m1.n42 - m1.n12*m1.n21*m1.n44 + m1.n11*m1.n22*m1.n44;
			m2.n34 = m1.n14*m1.n22*m1.n31 - m1.n12*m1.n24*m1.n31 - m1.n14*m1.n21*m1.n32 + m1.n11*m1.n24*m1.n32 + m1.n12*m1.n21*m1.n34 - m1.n11*m1.n22*m1.n34;
			m2.n41 = m1.n23*m1.n32*m1.n41 - m1.n22*m1.n33*m1.n41 - m1.n23*m1.n31*m1.n42 + m1.n21*m1.n33*m1.n42 + m1.n22*m1.n31*m1.n43 - m1.n21*m1.n32*m1.n43;
			m2.n42 = m1.n12*m1.n33*m1.n41 - m1.n13*m1.n32*m1.n41 + m1.n13*m1.n31*m1.n42 - m1.n11*m1.n33*m1.n42 - m1.n12*m1.n31*m1.n43 + m1.n11*m1.n32*m1.n43;
			m2.n43 = m1.n13*m1.n22*m1.n41 - m1.n12*m1.n23*m1.n41 - m1.n13*m1.n21*m1.n42 + m1.n11*m1.n23*m1.n42 + m1.n12*m1.n21*m1.n43 - m1.n11*m1.n22*m1.n43;
			m2.n44 = m1.n12*m1.n23*m1.n31 - m1.n13*m1.n22*m1.n31 + m1.n13*m1.n21*m1.n32 - m1.n11*m1.n23*m1.n32 - m1.n12*m1.n21*m1.n33 + m1.n11*m1.n22*m1.n33;
			m2.multiplyScalar( 1 / m1.determinant() );
			
			return m2;
		}
		
		/**
		 * Make Frustrum
		 * @param	left		Number
		 * @param	right		Number
		 * @param	bottom		Number
		 * @param	top			Number
		 * @param	near		Number
		 * @param	far			Number
		 * @return				Matrix
		 */
		public function makeFrustrum(left:Number, right:Number, bottom:Number, top:Number, near:Number, far:Number):Matrix {
			var m:Matrix, x:Number, y:Number, a:Number, b:Number, c:Number, d:Number;
			
			m = new Matrix();
			x = 2 * near / (right - left);
			y = 2 * near / (top - bottom);
			a = (right + left) / (right - left);
			b = (top + bottom) / (top - bottom);
			c = - (far + near) / (far - near);
			d = -2 * far * near / (far - near);
			
			m.n11 = x;  m.n12 = 0;  m.n13 = a;   m.n14 = 0;
			m.n21 = 0;  m.n22 = y;  m.n23 = b;   m.n24 = 0;
			m.n31 = 0;  m.n32 = 0;  m.n33 = c;   m.n34 = d;
			m.n41 = 0;  m.n42 = 0;  m.n43 = - 1; m.n44 = 0;
			
			return m;
		}
		
		/**
		 * Make Perspective
		 * @param	fov		Number
		 * @param	aspect	Number
		 * @param	near	Number
		 * @param	far		Number
		 * @return			Matrix
		 */
		public function makePerspective(fov:Number, aspect:Number, near:Number, far:Number):Matrix {
			var ymax:Number, ymin:Number, xmin:Number, xmax:Number;
			
			ymax = near * Math.tan( fov * Math.PI / 360 );
			ymin = - ymax;
			xmin = ymin * aspect;
			xmax = ymax * aspect;
			
			return makeFrustrum( xmin, xmax, ymin, ymax, near, far);			
		}
		
		/**
		 * Make Ortho
		 * @param	left	Number
		 * @param	right	Number
		 * @param	top		Number
		 * @param	bottom	Number
		 * @param	near	Number
		 * @param	far		Number
		 * @return			Matrix
		 */
		public function makeOrtho(left:Number, right:Number, top:Number, bottom:Number, near:Number, far:Number):Matrix {
			var m:Matrix, x:Number, y:Number, z:Number, w:Number, h:Number, p:Number;
			
			m = new Matrix();
			w = right - left;
			h = bottom - top;
			p = far - near;
			x = (right + left) / w;
			y = (bottom + top) / h;
			z = (far + near) / p;
			
			m.n11 = 2 / w; m.n12 = 0;     m.n13 = 0;      m.n14 = -x;
			m.n21 = 0;     m.n22 = 2 / h; m.n23 = 0;      m.n24 = -y;
			m.n31 = 0;     m.n32 = 0;     m.n33 = -2 / p; m.n34 = -z;
			m.n41 = 0;     m.n42 = 0;     m.n43 = 0;      m.n44 = 1;
			
			return m;
		}
		
		/**
		 * toString
		 * @return		String
		 */
		public function toString():String {
			return "| " + n11 + " | " + n12 + " | " + n13 + " | " + n14 + " |\n| "
						+ n21 + " | " + n22 + " | " + n23 + " | " + n24 + " |\n| "
						+ n31 + " | " + n32 + " | " + n33 + " | " + n34 + " |\n| "
						+ n41 + " | " + n42 + " | " + n43 + " | " + n44 + " |\n";			
		}
		
		/*********************************************
		 * Static Methods
		 *********************************************/
		
		/**
		 * Multiply
		 * @param	a	Matrix
		 * @param	b	Matrix
		 * @return		Matrix
		 */
		public static function multiply(a:Matrix, b:Matrix):Matrix {
			
			var m:Matrix = new Matrix();
			
			m.n11 = a.n11 * b.n11 + a.n12 * b.n21 + a.n13 * b.n31 + a.n14 * b.n41;
			m.n12 = a.n11 * b.n12 + a.n12 * b.n22 + a.n13 * b.n32 + a.n14 * b.n42;
			m.n13 = a.n11 * b.n13 + a.n12 * b.n23 + a.n13 * b.n33 + a.n14 * b.n43;
			m.n14 = a.n11 * b.n14 + a.n12 * b.n24 + a.n13 * b.n34 + a.n14 * b.n44;

			m.n21 = a.n21 * b.n11 + a.n22 * b.n21 + a.n23 * b.n31 + a.n24 * b.n41;
			m.n22 = a.n21 * b.n12 + a.n22 * b.n22 + a.n23 * b.n32 + a.n24 * b.n42;
			m.n23 = a.n21 * b.n13 + a.n22 * b.n23 + a.n23 * b.n33 + a.n24 * b.n43;
			m.n24 = a.n21 * b.n14 + a.n22 * b.n24 + a.n23 * b.n34 + a.n24 * b.n44;

			m.n31 = a.n31 * b.n11 + a.n32 * b.n21 + a.n33 * b.n31 + a.n34 * b.n41;
			m.n32 = a.n31 * b.n12 + a.n32 * b.n22 + a.n33 * b.n32 + a.n34 * b.n42;
			m.n33 = a.n31 * b.n13 + a.n32 * b.n23 + a.n33 * b.n33 + a.n34 * b.n43;
			m.n34 = a.n31 * b.n14 + a.n32 * b.n24 + a.n33 * b.n34 + a.n34 * b.n44;

			m.n41 = a.n41 * b.n11 + a.n42 * b.n21 + a.n43 * b.n31 + a.n44 * b.n41;
			m.n42 = a.n41 * b.n12 + a.n42 * b.n22 + a.n43 * b.n32 + a.n44 * b.n42;
			m.n43 = a.n41 * b.n13 + a.n42 * b.n23 + a.n43 * b.n33 + a.n44 * b.n43;
			m.n44 = a.n41 * b.n14 + a.n42 * b.n24 + a.n43 * b.n34 + a.n44 * b.n44;
			
			return m;
		}
		
	}

}